package pprg;

import java.util.Random;

public class Philosopher implements Runnable  {

	private Random rnd = new Random();

	private long thinkTime;
	private long eatTime;
	private int index;
	
	private long totalEatingTime;
	private int timesEaten; 
	
	private ForkOrder forkOrder;
	
	private boolean terminated;

	public Philosopher(int index, ForkOrder forkOrder, long thinkTime,
			long eatTime) {
		this.index = index;
		this.thinkTime = thinkTime;
		this.eatTime = eatTime;
		this.forkOrder = forkOrder;
		terminated = false;
	}

	public void eat(long time) {
		totalEatingTime += time;
		timesEaten++;
		System.out.println(String.format("%s ms: %s is eating",
				DiningPhilosophers.getRuntime(), this.toString()));
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void think(long time) {
		System.out.println(String.format("%s ms: %s is thinking",
				DiningPhilosophers.getRuntime(), this.toString()));
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		while (!isTerminated()) {
			try {
				think(rnd.nextInt((int) thinkTime));
				if (pickupFirstFork() && pickupSecondFork()) {
					eat(rnd.nextInt((int) eatTime));
				}
				putDownForks();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private void putDownForks() {
		if (forkOrder.getFirstFork().isHeldByCurrentThread()) {
			forkOrder.getFirstFork().unlock();
			System.out.println(String.format("%s ms: %s put down left %s",
					DiningPhilosophers.getRuntime(), this.toString(),
					forkOrder.getFirstFork().toString()));
		}

		if (forkOrder.getSecondFork().isHeldByCurrentThread()) {
			forkOrder.getSecondFork().unlock();
			System.out.println(String.format("%s ms: %s put down right %s",
					DiningPhilosophers.getRuntime(), this.toString(),
					forkOrder.getSecondFork().toString()));
		}
	}

	private boolean pickupFirstFork() throws InterruptedException {
		
		System.out.println(String.format("%s ms: %s is waiting for left %s",
				DiningPhilosophers.getRuntime(), this.toString(),
				forkOrder.getFirstFork().toString()));

		while (!forkOrder.getFirstFork().isHeldByCurrentThread() && !isTerminated()) {
			forkOrder.getFirstFork().tryLock();
		}
		if (!isTerminated()) {
			System.out.println(String.format("%s ms: %s took the left %s",
					DiningPhilosophers.getRuntime(), this.toString(),
					forkOrder.getFirstFork().toString()));
			return true;
		} else {
			return false;
		}

	}

	private boolean pickupSecondFork() throws InterruptedException {
		
		System.out.println(String.format("%s ms: %s is waiting for right %s",
				DiningPhilosophers.getRuntime(), this.toString(),
				forkOrder.getSecondFork().toString()));

		while (!forkOrder.getSecondFork().isHeldByCurrentThread() && !isTerminated()) {
			forkOrder.getSecondFork().tryLock();
		}
		
		if (!isTerminated()) {
			System.out.println(String.format("%s ms: %s took the right Fork %s",
					DiningPhilosophers.getRuntime(), this.toString(),
					forkOrder.getSecondFork().toString()));
			return true;
		} else {
			return false;
		}
	}

	@Override
	public String toString() {
		return "Philosopher #" + this.index + " ";
	}

	public boolean isTerminated() {
		return terminated;
	}

	public void setTerminated(boolean terminated) {
		this.terminated = terminated;
	}

	public long getTotalEatingTime() {
		return totalEatingTime;
	}

	public int getTimesEaten() {
		return timesEaten;
	}

	public int getIndex() {
		return index;
	}
}
